Skip to content

fix(qwen-code): OAuth headers, WAF detection, and upstream alignment#154

Open
b3nw wants to merge 1 commit intoMirrowel:devfrom
b3nw:feature/qwen-code-all
Open

fix(qwen-code): OAuth headers, WAF detection, and upstream alignment#154
b3nw wants to merge 1 commit intoMirrowel:devfrom
b3nw:feature/qwen-code-all

Conversation

@b3nw
Copy link
Copy Markdown
Contributor

@b3nw b3nw commented Apr 12, 2026

Summary

This PR refactors the Qwen Code provider to improve stability in token rotation, clean up request headers, and add robust mitigations against upstream Alibaba Cloud WAF blocks. It also resolves a structural circular dependency in the provider imports.

Key Changes

  • Resolved Circular Dependency: Moved DEFAULT_DASHSCOPE_BASE_URL and QWEN_USER_AGENT constants to the lower-level qwen_auth_base.py module to establish a single source of truth without fragile delayed imports.
  • WAF Blocking Mitigation: Added a heuristic-based detection mechanism for Alibaba Cloud WAF. Token refreshes now detect false-positive 200 OK (HTML payload) interceptions and trigger an exponential backoff rather than crashing the refresh cycle with a JSONDecodeError.
  • Fixed env:// Token Rotation: Updated proactively_refresh() to bypass fresh disk reads for virtual env:// targets. This preserves in-memory rotated tokens and resolves the HTTP 400 Bad Request loop during subsequent refreshes.
  • Stable URL Normalization: Added internal DashScope URL normalization to dynamically handle missing protocols and correctly append /v1 suffixes, fixing the previous duplication bug (/v1/v1/chat/completions).
  • Header Clean-up: Stripped outdated Google/Gemini spoofing headers in favor of explicit X-DashScope-* headers and a unified User-Agent, supplemented by request UUIDs for improved telemetry tracking.

@b3nw b3nw requested a review from Mirrowel as a code owner April 12, 2026 03:32
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Support forwarding request metadata and added request identifiers for interactive auth flows.
  • Bug Fixes

    • Preserve rotated tokens when credentials come from environment sources.
    • Retry and better handle non-JSON auth responses to mitigate WAF/network blocks.
  • Improvements

    • Standardized API base URL handling, default endpoint behavior, and unified user-agent/auth headers.

Walkthrough

Consolidates Qwen/DashScope authentication defaults, preserves rotated tokens for env-backed credentials, adds WAF-detection and retry for token refreshes, normalizes OAuth API base URLs, updates Qwen endpoints and request headers, and attaches request IDs to device-flow calls.

Changes

Cohort / File(s) Summary
Auth core
src/rotator_library/providers/qwen_auth_base.py
Added DEFAULT_DASHSCOPE_BASE_URL, QWEN_USER_AGENT, and uuid import; introduced _normalize_api_base_url(); added x-request-id to device-flow calls.
Token refresh & retries
src/rotator_library/providers/qwen_auth_base.py
Adjusted _refresh_token to skip disk reload for env:// credentials; detect non-JSON (likely WAF) responses, log warnings, and retry with exponential backoff up to max_retries.
API details & credential handling
src/rotator_library/providers/qwen_auth_base.py
Expanded get_api_details to handle three credential forms (file path, env://, direct API key) and default direct keys to DEFAULT_DASHSCOPE_BASE_URL; normalize resource_url (ensure https:// and /v1).
Provider integration & headers
src/rotator_library/providers/qwen_code_provider.py
Added "metadata" to SUPPORTED_PARAMS; removed /v1 prefix from model and chat endpoints; replaced Google-specific headers with DashScope headers (X-DashScope-CacheControl, X-DashScope-UserAgent, optional X-DashScope-AuthType); imported DEFAULT_DASHSCOPE_BASE_URL and QWEN_USER_AGENT.
Auth type detection
src/rotator_library/providers/qwen_code_provider.py
Added guarded is_oauth determination (handles OSError/ValueError) to decide inclusion of X-DashScope-AuthType header.

Sequence Diagram(s)

sequenceDiagram
participant Client
participant QwenProvider as Provider
participant TokenEndpoint as OAuthServer
participant DiskEnv as Storage

Client->>Provider: Request action (chat/completion)
Provider->>DiskEnv: Determine credential (file / env:// / api key)
alt credential is env:// or file (oauth)
    Provider->>TokenEndpoint: Use access token (may refresh)
    TokenEndpoint-->>Provider: 200 JSON or non-JSON (WAF)
    alt non-JSON (WAF)
        Provider->>TokenEndpoint: Retry with backoff (x attempts)
        TokenEndpoint-->>Provider: Final response
    end
    Provider->>TokenEndpoint: Poll device flow (includes x-request-id)
    TokenEndpoint-->>Provider: Token
else direct API key
    Provider->>QwenProvider: Call API using DEFAULT_DASHSCOPE_BASE_URL
end
Provider->>Client: Return result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 In a burrow of headers and tiny cues,
I stitched a UUID for curious news,
Env-tokens rest safe beneath the sod,
Retries hop back when WAFs give nod,
DashScope lights the tunnel — hop, rejoice, muse!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description provides comprehensive context covering key changes, objectives, and rationale. However, the required template sections (Testing Done and Checklist) are not completed. Complete the 'Testing Done' section and 'Checklist' to document how changes were tested and verify all required items have been addressed.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main changes: OAuth headers cleanup, WAF detection mitigation, and upstream API alignment with version prefix handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 12, 2026

Greptile Summary

This PR refactors the Qwen Code OAuth provider to resolve a circular dependency, add WAF detection with exponential backoff, fix env:// token rotation, normalize DashScope URLs, and replace Gemini spoofing headers with explicit X-DashScope-* headers. All three issues flagged in the previous review thread (trailing-slash /v1 duplication, WAF ValueError bypassing backoff, and unconditional X-DashScope-AuthType) appear to be correctly addressed in the current code.

Confidence Score: 5/5

Safe to merge; all remaining findings are P2 style/robustness suggestions with no blocking correctness issues.

The three P1 concerns from prior review rounds are resolved in the current code. New findings are limited to a brittle string comparison that works correctly in the common case, a missing .get() guard on a standard OAuth field, and two unused imports — none of which affect production correctness on the happy path.

No files require special attention beyond the minor suggestions in qwen_auth_base.py and qwen_code_provider.py.

Important Files Changed

Filename Overview
src/rotator_library/providers/qwen_auth_base.py Adds WAF detection (content-type heuristic), env:// token rotation fix, URL normalization, and constants consolidation; brittle portal.qwen.ai exact-string redirect and a missing .get() guard on expires_in are minor concerns.
src/rotator_library/providers/qwen_code_provider.py Replaces Gemini spoofing headers with DashScope-native headers, adds is_oauth guard for X-DashScope-AuthType, and cleans up constants; uuid and datetime imports are unused.

Sequence Diagram

sequenceDiagram
    participant P as QwenCodeProvider
    participant A as QwenAuthBase
    participant Q as Qwen Token Endpoint
    participant D as DashScope API

    P->>A: get_api_details(credential)
    A->>A: is_oauth? (env:// or file path)
    A->>A: _load_credentials(path)
    alt token expired
        A->>A: _refresh_token(path)
        loop up to 3 retries
            A->>Q: POST /oauth2/token (refresh_token)
            Q-->>A: HTTP 200
            alt content-type contains json
                A->>A: parse token, update cache + disk
            else WAF block (HTML body)
                A->>A: set last_error, exponential backoff sleep
            end
        end
        alt new_token_data is None
            A->>A: increment _refresh_failures, set _next_refresh_after
            A--xP: raise last_error (backoff applied)
        end
    end
    A->>A: portal.qwen.ai hostname check, use DashScope default
    A->>A: _normalize_api_base_url(resource_url)
    A-->>P: (api_base, access_token)
    P->>D: POST /v1/chat/completions (X-DashScope-AuthType if OAuth)
    D-->>P: SSE stream
    P->>P: _convert_chunk_to_openai() per chunk
    P-->>P: yield litellm.ModelResponse
Loading

Reviews (2): Last reviewed commit: "fix(qwen-code): OAuth headers, WAF detec..." | Re-trigger Greptile

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/rotator_library/providers/qwen_code_provider.py (1)

610-617: ⚠️ Potential issue | 🟡 Minor

Conditional X-DashScope-AuthType header required for non-OAuth credentials.

get_api_details() supports direct API keys (when credential_identifier is not a file path or env:// path), but line 617 unconditionally sends "X-DashScope-AuthType": "qwen-oauth". This causes direct API key requests to misidentify their auth mechanism, which skews telemetry and may trip upstream auth validation. The header should be set conditionally based on credential type, or omitted entirely for non-OAuth calls.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/rotator_library/providers/qwen_auth_base.py`:
- Around line 588-595: The env-backed credentials still carry the old injected
portal URL so the fallback to DEFAULT_DASHSCOPE_BASE_URL never triggers; update
the branch that reads creds.get("resource_url") in qwen_auth_base.py to treat a
missing or sentinel portal value as "unset" (e.g. if resource_url is None or
equals "https://portal.qwen.ai/v1") and in that case use
DEFAULT_DASHSCOPE_BASE_URL; keep using
self._normalize_api_base_url(resource_url, DEFAULT_DASHSCOPE_BASE_URL) when
resource_url is a real custom URL, and otherwise set base_url =
DEFAULT_DASHSCOPE_BASE_URL and access_token appropriately (affecting the code
that references resource_url, creds, and credential_identifier).
- Around line 615-619: The normalization logic for resource_url incorrectly
checks url.endswith("/v1") before removing trailing slashes, causing duplicates;
update the code around resource_url/url so you strip trailing slashes first
(e.g., call resource_url.rstrip("/") or url = url.rstrip("/")) and then check
endswith("/v1") and only append "/v1" if missing, ensuring variables like
resource_url, url, and the endswith("/v1") condition are adjusted accordingly.
- Around line 399-414: The token-refresh block in _refresh_token() currently
raises a ValueError on the final non-JSON response, bypassing the shared failure
handling that updates _refresh_failures and _next_refresh_after; instead capture
that failure into last_error (e.g., create the ValueError with the same message
and assign it to last_error) and avoid raising immediately so the function falls
through to the existing "new_token_data is None" branch which uses last_error to
update _refresh_failures/_next_refresh_after; keep the same backoff/continue
behavior for attempt < max_retries - 1 and only set last_error (not raise) when
max_retries is exhausted so the common failure path handles it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 99205105-a7b7-4415-b4a6-7a42d96f17fc

📥 Commits

Reviewing files that changed from the base of the PR and between b62f6e4 and 3786000.

📒 Files selected for processing (2)
  • src/rotator_library/providers/qwen_auth_base.py
  • src/rotator_library/providers/qwen_code_provider.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Greptile Review
  • GitHub Check: review-pr
🧰 Additional context used
🪛 Ruff (0.15.9)
src/rotator_library/providers/qwen_auth_base.py

[warning] 404-406: Logging statement uses f-string

(G004)


[warning] 411-414: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 572-572: Async functions should not use os.path methods, use trio.Path or anyio.path

(ASYNC240)


[warning] 581-581: Logging statement uses f-string

(G004)

@b3nw b3nw force-pushed the feature/qwen-code-all branch from 3786000 to 0e4993e Compare April 12, 2026 03:59
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/rotator_library/providers/qwen_auth_base.py`:
- Around line 870-875: The current headers dict (containing "x-request-id":
str(uuid.uuid4())) is generated once and reused across the device-code request
and subsequent polling; change the code so a fresh x-request-id is created for
every outbound HTTP call by generating the header value at request time rather
than once at module/flow init — update the places that build/send requests (the
headers construction referenced as headers in qwen_auth_base.py around the
device-code request and the polling loop at the later block) to re-create the
headers dict per call (keeping QWEN_USER_AGENT and Content-Type unchanged) so
each request gets a unique x-request-id.
- Around line 572-598: get_api_details() currently only treats strings starting
with "env://" or existing files as OAuth, so passing a non-existent credential
path (e.g. "oauth_creds/whatever.json") falls through to the direct-API-key
branch; change the detection so credential paths that look like file-based
credentials are treated as OAuth even if the file is missing so
_load_credentials() can perform its env-file fallback. Replace the is_oauth
calculation (the block using os.path.isfile) to mark OAuth when
credential_identifier.startswith("env://") OR
credential_identifier.endswith(".json") OR "/" in credential_identifier (i.e.,
appears to be a path), then continue to call
self._load_credentials(credential_identifier), self._is_token_expired(creds) and
self._refresh_token(credential_identifier) as before.
- Around line 354-365: The current refresh logic unconditionally re-reads from
disk for any path not starting with "env://", which breaks cached credentials
that were originally loaded from env vars via _load_credentials(); change the
condition in the refresh block so _read_creds_from_file(path) is only invoked
when the path actually points to an existing file (e.g., check file existence
before calling _read_creds_from_file), otherwise use the cached entry in
self._credentials_cache[path]; update the condition around the call to
_read_creds_from_file in the refresh flow that references path,
_read_creds_from_file, and _credentials_cache so env-backed cached entries
aren’t forced to hit disk.

In `@src/rotator_library/providers/qwen_code_provider.py`:
- Around line 610-624: The duplicate env:// / file-path credential detection in
QwenCodeProvider should be replaced with the shared helper in QwenAuthBase to
keep token-source logic centralized: remove the try/except block that computes
is_oauth from credential_path and instead call the existing
QwenAuthBase.get_api_details (or extract a small is_oauth helper from it) to
determine whether the token is OAuth, then set headers["X-DashScope-AuthType"] =
"qwen-oauth" when that helper indicates OAuth; update QwenCodeProvider to
reference the QwenAuthBase method (e.g., via self.auth_base.get_api_details or
the extracted helper) so the token classification and header logic remain in
lockstep.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8416d8b8-043d-4e00-8b21-c1d71348a06d

📥 Commits

Reviewing files that changed from the base of the PR and between 3786000 and 0e4993e.

📒 Files selected for processing (2)
  • src/rotator_library/providers/qwen_auth_base.py
  • src/rotator_library/providers/qwen_code_provider.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
🪛 Ruff (0.15.9)
src/rotator_library/providers/qwen_code_provider.py

[warning] 611-611: Async functions should not use os.path methods, use trio.Path or anyio.path

(ASYNC240)

src/rotator_library/providers/qwen_auth_base.py

[warning] 408-410: Logging statement uses f-string

(G004)


[warning] 573-573: Async functions should not use os.path methods, use trio.Path or anyio.path

(ASYNC240)


[warning] 582-582: Logging statement uses f-string

(G004)

Comment on lines +354 to 365
# [ROTATING TOKEN FIX] Read fresh credentials before refresh.
# Qwen uses rotating refresh tokens - each refresh invalidates the previous token.
# If we use a stale cached token, refresh will fail with HTTP 400.
# Reading fresh from disk ensures we have the latest token.
await self._read_creds_from_file(path)
if not path.startswith("env://"):
# For file paths, read fresh from disk to pick up tokens that may have
# been updated by another process or a previous refresh cycle.
await self._read_creds_from_file(path)
# For env:// paths, the in-memory cache is the single source of truth.
# _save_credentials updates the cache after each refresh, so the cache
# always holds the latest rotating tokens. Re-reading from static env vars
# would discard the rotated refresh_token and break subsequent refreshes.
creds_from_file = self._credentials_cache[path]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't force a disk reread for cached env-backed credentials.

_load_credentials() still supports the backwards-compatible path where a missing file falls back to env vars and caches the result (src/rotator_library/providers/qwen_auth_base.py Lines 230-242). This branch only exempts env://, so refreshing one of those cached env-backed credentials now raises on the nonexistent file before the refresh even starts.

Suggested fix
-            if not path.startswith("env://"):
+            loaded_from_env = cached_creds and cached_creds.get(
+                "_proxy_metadata", {}
+            ).get("loaded_from_env")
+            if not path.startswith("env://") and not loaded_from_env:
                 # For file paths, read fresh from disk to pick up tokens that may have
                 # been updated by another process or a previous refresh cycle.
                 await self._read_creds_from_file(path)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rotator_library/providers/qwen_auth_base.py` around lines 354 - 365, The
current refresh logic unconditionally re-reads from disk for any path not
starting with "env://", which breaks cached credentials that were originally
loaded from env vars via _load_credentials(); change the condition in the
refresh block so _read_creds_from_file(path) is only invoked when the path
actually points to an existing file (e.g., check file existence before calling
_read_creds_from_file), otherwise use the cached entry in
self._credentials_cache[path]; update the condition around the call to
_read_creds_from_file in the refresh flow that references path,
_read_creds_from_file, and _credentials_cache so env-backed cached entries
aren’t forced to hit disk.

Comment on lines +572 to 598
try:
is_oauth = credential_identifier.startswith("env://") or os.path.isfile(
credential_identifier
)
except (OSError, ValueError):
# os.path.isfile can raise on invalid path strings (e.g. very long API keys)
is_oauth = False

if is_oauth:
lib_logger.debug(
f"Using OAuth credentials from file: {credential_identifier}"
f"Using OAuth credentials from: {credential_identifier}"
)
creds = await self._load_credentials(credential_identifier)

if self._is_token_expired(creds):
creds = await self._refresh_token(credential_identifier)

base_url = creds.get("resource_url", "https://portal.qwen.ai/v1")
if not base_url.startswith("http"):
base_url = f"https://{base_url}"
resource_url = creds.get("resource_url")
if resource_url == "https://portal.qwen.ai/v1":
resource_url = None
base_url = self._normalize_api_base_url(resource_url, DEFAULT_DASHSCOPE_BASE_URL)
access_token = creds["access_token"]
else:
# Direct API key: use as-is
# Direct API key: use as-is with DashScope default
lib_logger.debug("Using direct API key for Qwen Code")
base_url = "https://portal.qwen.ai/v1"
base_url = DEFAULT_DASHSCOPE_BASE_URL
access_token = credential_identifier
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This classifier bypasses the documented missing-file env fallback.

Only env:// and existing files are treated as OAuth here. That makes the _load_credentials() fallback for nonexistent file paths (src/rotator_library/providers/qwen_auth_base.py Lines 230-242) unreachable from get_api_details(), so a stateless deployment that still passes oauth_creds/...json will now send the literal path as the bearer token.

🧰 Tools
🪛 Ruff (0.15.9)

[warning] 573-573: Async functions should not use os.path methods, use trio.Path or anyio.path

(ASYNC240)


[warning] 582-582: Logging statement uses f-string

(G004)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rotator_library/providers/qwen_auth_base.py` around lines 572 - 598,
get_api_details() currently only treats strings starting with "env://" or
existing files as OAuth, so passing a non-existent credential path (e.g.
"oauth_creds/whatever.json") falls through to the direct-API-key branch; change
the detection so credential paths that look like file-based credentials are
treated as OAuth even if the file is missing so _load_credentials() can perform
its env-file fallback. Replace the is_oauth calculation (the block using
os.path.isfile) to mark OAuth when credential_identifier.startswith("env://") OR
credential_identifier.endswith(".json") OR "/" in credential_identifier (i.e.,
appears to be a path), then continue to call
self._load_credentials(credential_identifier), self._is_token_expired(creds) and
self._refresh_token(credential_identifier) as before.

Comment on lines 870 to 875
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"User-Agent": QWEN_USER_AGENT,
"Content-Type": "application/x-www-form-urlencoded",
"Accept": "application/json",
"x-request-id": str(uuid.uuid4()),
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Regenerate x-request-id per outbound request.

x-request-id is created once and then reused for the device-code request plus every polling call, so the telemetry is per-flow, not per-request.

Suggested fix
-        headers = {
+        base_headers = {
             "User-Agent": QWEN_USER_AGENT,
             "Content-Type": "application/x-www-form-urlencoded",
             "Accept": "application/json",
-            "x-request-id": str(uuid.uuid4()),
         }
         async with httpx.AsyncClient() as client:
             request_data = {
                 "client_id": CLIENT_ID,
                 "scope": SCOPE,
@@
                 dev_response = await client.post(
                     "https://chat.qwen.ai/api/v1/oauth2/device/code",
-                    headers=headers,
+                    headers={**base_headers, "x-request-id": str(uuid.uuid4())},
                     data=request_data,
                 )
@@
                     poll_response = await client.post(
                         TOKEN_ENDPOINT,
-                        headers=headers,
+                        headers={**base_headers, "x-request-id": str(uuid.uuid4())},
                         data={
                             "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
                             "device_code": dev_data["device_code"],
                             "client_id": CLIENT_ID,
                             "code_verifier": code_verifier,

Also applies to: 947-956

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rotator_library/providers/qwen_auth_base.py` around lines 870 - 875, The
current headers dict (containing "x-request-id": str(uuid.uuid4())) is generated
once and reused across the device-code request and subsequent polling; change
the code so a fresh x-request-id is created for every outbound HTTP call by
generating the header value at request time rather than once at module/flow init
— update the places that build/send requests (the headers construction
referenced as headers in qwen_auth_base.py around the device-code request and
the polling loop at the later block) to re-create the headers dict per call
(keeping QWEN_USER_AGENT and Content-Type unchanged) so each request gets a
unique x-request-id.

Comment on lines +610 to +624
try:
is_oauth = credential_path.startswith("env://") or os.path.isfile(credential_path)
except (OSError, ValueError):
is_oauth = False

headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"Accept": "text/event-stream",
"User-Agent": "google-api-nodejs-client/9.15.1",
"X-Goog-Api-Client": "gl-node/22.17.0",
"Client-Metadata": "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI",
"User-Agent": QWEN_USER_AGENT,
"X-DashScope-CacheControl": "enable",
"X-DashScope-UserAgent": QWEN_USER_AGENT,
}
if is_oauth:
headers["X-DashScope-AuthType"] = "qwen-oauth"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract OAuth credential detection into one shared helper.

This block reimplements the same env:///file-path heuristic as QwenAuthBase.get_api_details() in src/rotator_library/providers/qwen_auth_base.py Lines 572-579. The token source and X-DashScope-AuthType need to stay in lockstep, so keeping two copies will drift again the next time credential classification changes.

🧰 Tools
🪛 Ruff (0.15.9)

[warning] 611-611: Async functions should not use os.path methods, use trio.Path or anyio.path

(ASYNC240)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/rotator_library/providers/qwen_code_provider.py` around lines 610 - 624,
The duplicate env:// / file-path credential detection in QwenCodeProvider should
be replaced with the shared helper in QwenAuthBase to keep token-source logic
centralized: remove the try/except block that computes is_oauth from
credential_path and instead call the existing QwenAuthBase.get_api_details (or
extract a small is_oauth helper from it) to determine whether the token is
OAuth, then set headers["X-DashScope-AuthType"] = "qwen-oauth" when that helper
indicates OAuth; update QwenCodeProvider to reference the QwenAuthBase method
(e.g., via self.auth_base.get_api_details or the extracted helper) so the token
classification and header logic remain in lockstep.

@Mirrowel
Copy link
Copy Markdown
Owner

There are some notable things raised in the comments. Gonna apply some fixes and then merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants